package edu.hawaii.ics314.ical.model;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * Represents a specific point in time, either with or without a date component.
 * 
 * @author 
 *
 */
public class DateTime implements Comparable<DateTime>{
	
	public enum DateTimeComponent {YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND}
	
	private final long TIME;
	private final boolean HAS_DATE_COMPONENT;
	
	public DateTime(long time) {
		TIME = time;
		HAS_DATE_COMPONENT = true;
	}
	
	/**
	 * Constructor that creates an instance of <code>DateTime</code>.
	 * 
	 * @param year - the year.
	 * @param month - the month.
	 * @param day - the day.
	 * @param hour - the hour.
	 * @param minute - the minutes.
	 * @param second - the seconds.
	 */
	public DateTime(int year, int month, int day, int hour, int minute, int second) {
		GregorianCalendar calendar = new GregorianCalendar();
		// Decrement month value to convert from 1-based numbering to 0-based numbering.
		calendar.set(year, (month - 1), day, hour, minute, second);
		calendar.set(GregorianCalendar.MILLISECOND, 0);
		TIME = calendar.getTimeInMillis();
		HAS_DATE_COMPONENT = (year != 0 && month != 0 && day != 0);
	}
	
	/**
	 * Returns if this <code>DateTime</code> has a date.
	 * 
	 * @return If the event has a date.
	 */
	public boolean hasDateComponent() {
		return HAS_DATE_COMPONENT;
	}
	
	/**
	 * Returns if the time of the <code>DateTime</code> is midnight.
	 * 
	 * @return If the time of the <code>DateTime</code> is midnight.
	 */
	public boolean isMidnight() {
		return getComponentValue(DateTimeComponent.HOUR) == 0
				&& getComponentValue(DateTimeComponent.MINUTE) == 0
				&& getComponentValue(DateTimeComponent.SECOND) == 0
				&& getComponentValue(DateTimeComponent.MILLISECOND) == 0;
	}
	
	/**
	 * Returns a specific <code>Property</code> of the <code>DateTime</code>.
	 * 
	 * @param component - A <code>Property</code> of the <code>DateTime</code>.
	 * @return The specified <code>Property</code> of the <code>DateTime</code>.
	 */
	public int getComponentValue(DateTimeComponent component) {
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(new Date(TIME));
		switch(component) {
		case YEAR:
			return calendar.get(GregorianCalendar.YEAR);
		case MONTH:
			return calendar.get(GregorianCalendar.MONTH) + 1; // Convert from 0-based numbering to 1-based.
		case DAY:
			return calendar.get(GregorianCalendar.DAY_OF_MONTH);
		case HOUR:
			return calendar.get(GregorianCalendar.HOUR_OF_DAY);
		case MINUTE:
			return calendar.get(GregorianCalendar.MINUTE);
		case SECOND:
			return calendar.get(GregorianCalendar.SECOND);
		case MILLISECOND:
			return calendar.get(GregorianCalendar.MILLISECOND);
		default:
			throw new IllegalArgumentException("unrecognized DateTimeComponent");
		}
	}
	
	/**
	 * Determines how much time passes between two <code>DateTime</code>s.
	 * 
	 * @param other - the <code>DateTime</code>s being compared.
	 * @return The time that passed.
	 * @throws IllegalArgumentException if both <code>DateTime</code> instances do not have 
	 * the same <code>DateComponent</code> value.
	 */
	public TimeSpan elapsed(DateTime other) {
		if(hasDateComponent() == other.hasDateComponent()) {
			return new TimeSpan(this, other);
		} else {
			throw new IllegalArgumentException("both DateTime instances must have the same hasDateComponent() value");
		}
	}
	
	/**
	 * Returns the time of the <code>DateTime</code>.
	 * 
	 * @param unit - The specified <code>TimeUnit</code>.
	 * @return The time of the <code>DateTime</code>.
	 */
	public long toLong(TimeUnit unit) {
		return TIME;
	}
	
	@Override
	public int compareTo(DateTime other) {
		if(hasDateComponent() == other.hasDateComponent()) {
			return (int) (TIME - other.TIME);
		} else {
			throw new IllegalArgumentException("both DateTime instances must have the same hasDateComponent() value");
		}
	}
	
	@Override
	public boolean equals(Object other) {
		if(other instanceof DateTime) {
			DateTime dtOther = (DateTime) other;
			return hasDateComponent() == dtOther.hasDateComponent() && TIME == dtOther.TIME;
		} else {
			return false;
		}
	}
	
	@Override
	public String toString() {
		return new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date(TIME));
	}
	
	/**
	 * Returns the current time.
	 * 
	 * @return The current time.
	 */
	public static DateTime now() {
		return new DateTime(System.currentTimeMillis());
	}
	
	/**
	 * Creating a new <code>DateTime</code> with the specified parameters.
	 * 
	 * @param year - the year.
	 * @param month - the month.
	 * @param day - the day.
	 * 
	 * @return The new date of the event.
	 */
	public static DateTime fromDate(int year, int month, int day) {
		return new DateTime(year, month, day, 0, 0, 0);
	}
	
	/**
	 * Creating a new <code>DateTime</code> with the specified parameters.
	 * 
	 * @param hour - the hour.
	 * @param minute - The minutes.
	 * @param second - the seconds.
	 * 
	 * @return The new time for the event.
	 */
	public static DateTime fromTime(int hour, int minute, int second) {
		return new DateTime(0, 0, 0, hour, minute, second);
	}
	
	/**
	 * Creating a new <code>DateTime</code> with the specified parameters.
	 * 
	 * @param date - the date.
	 * @param hour - the hour.
	 * @param minute - the minutes.
	 * @param second - the seconds.
	 * @return The time of the event.
	 */
	public static DateTime fromTime(DateTime date, int hour, int minute, int second) {
		return new DateTime(date.getComponentValue(DateTimeComponent.YEAR),
				date.getComponentValue(DateTimeComponent.MONTH),
				date.getComponentValue(DateTimeComponent.DAY), hour, minute, second);
	}
	
	/**
	 * Parses a string to create a new <code>DateTime</code>.
	 * 
	 * @param dtValue - the string.
	 * @return A new <code>DateTime</code>.
	 * @throws ParseException if it is unable to parse the date.
	 */
	public static DateTime parse(String dtValue) throws ParseException {
		Objects.requireNonNull(dtValue);
		SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
		return new DateTime(dateTimeFormat.parse(dtValue).getTime());
	}
	
}
